//////////////////////////////////////////////////////////////////////////////////
//	Copyright 2011 by Yosef Grabivker, ryuho@homenetworksoft.com
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.
//////////////////////////////////////////////////////////////////////////////////
#include <time.h>

#ifdef WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#endif

#include "ipresolver.h"

#define MAX_JOB_SIZE	2000
#define MAX_RES_DB_SIZE 4000

CIPResolver::CIPResolver(void):wxThread(wxTHREAD_JOINABLE), m_pObserver(NULL)
{
}

CIPResolver::~CIPResolver(void)
{
	Stop();
}

void CIPResolver::Start()
{
	if ( wxTHREAD_NO_ERROR==Create() )
	{
		SetPriority(WXTHREAD_MIN_PRIORITY);

		if ( wxTHREAD_NO_ERROR !=Run() )
		{

		}
	}
}

void CIPResolver::CleanJobList()
{
	wxCriticalSectionLocker locker(m_csJobsGuard);

	if ( !m_jobs.empty() )
	{
		m_jobs.erase(m_jobs.begin(), m_jobs.end());
	}
}


void CIPResolver::Stop()
{
	m_bWorking = false;

	if ( IsAlive() )
	{
		wxThread::Wait();
	}
}

bool CIPResolver::GetJob(JOBENTRY& entry)
{
	bool bRes(false);

	wxCriticalSectionLocker locker(m_csJobsGuard);

	if ( !m_jobs.empty() )
	{
		JOBLIST::iterator i = m_jobs.begin();
		entry = i->second;
		m_jobs.erase( i );

		bRes = true;
	}

	return bRes;
}

void* CIPResolver::Entry()
{
	void* pRet(NULL);

	m_bWorking = m_bThreadRun = true;

	while ( m_bWorking )
	{
		JOBENTRY entry;

		if ( GetJob(entry) )
		{
			ResolveEng(entry);
		}
		else
		{
			wxThread::Sleep(20);
		}
	}

	m_bThreadRun = false;

	return pRet;
}

void CIPResolver::ResolveEng(JOBENTRY& jobentry)
{
	bool bResolved(false);
	RESENTRY entry;
	entry.tLast = time(NULL);

	#ifdef WIN32
	TCHAR hostname[NI_MAXHOST];
	TCHAR servInfo[NI_MAXSERV];
	struct sockaddr_in addr;
	addr.sin_addr.s_addr = jobentry.lIp;
	addr.sin_family = AF_INET;
	addr.sin_port = 0;

	//works better
	DWORD dwRetval = GetNameInfo((struct sockaddr *) &addr,
		sizeof(addr),
		hostname,
		NI_MAXHOST, servInfo, NI_MAXSERV, NI_NUMERICSERV);

	if (!dwRetval)
	{
		entry.strDNSName = hostname;
		bResolved = true;
	}
	#else
	struct in_addr addr = { 0 };
	struct hostent *remoteHost = NULL;
	addr.s_addr = jobentry.lIp;

	remoteHost = gethostbyaddr( (char *) &addr, sizeof(addr), AF_INET|AF_NETBIOS);
	if ( remoteHost )
	{
		entry.strDNSName = remoteHost->h_name;
		bResolved = true;
	}
	#endif


	if ( bResolved )
	{
		{
			wxCriticalSectionLocker locker(m_csEntriesGuard);

			m_resolvedb[jobentry.lIp] = entry;

			if ( m_resolvedb.size()>MAX_RES_DB_SIZE  )
			{
				time_t t=time(NULL);
				t-=60*60;

				while( !ShrinkDB( m_resolvedb, t, MAX_RES_DB_SIZE ) )
				{
					t+=60;
				}
			}
		}

		if ( m_pObserver )
		{
			for(JOBENTRY::COOKIESLIST::iterator i = jobentry.m_lstCookies.begin();i!=jobentry.m_lstCookies.end();++i)
			{
				m_pObserver->OnResolve( jobentry.lIp, entry.strDNSName, *i );
			}
		}
	}
}

template<typename T>
bool CIPResolver::ShrinkDB(T& db, time_t t, size_t tMax )
{
	bool bRes(false);

	try
	{
		T resolvedb;

		for(T::iterator i = db.begin();i != db.end();++i)
		{
			if ( i->second.tLast>t )
			{
				resolvedb[ i->first ] = i->second;
			}
		}

		if ( resolvedb.size()<tMax  )
		{
			bRes = true;
			db = resolvedb;
		}
	}
	catch (...)
	{
	}

	return bRes;
}

bool CIPResolver::Resolve(unsigned long lIP, void* pCookie, wxString& strHostName)
{
	try
	{
		{
			wxCriticalSectionLocker locker(m_csEntriesGuard);
			
			RESENTRIES::iterator i = m_resolvedb.find( lIP );

			if ( i!=m_resolvedb.end() )
			{
				strHostName = i->second.strDNSName;
				i->second.tLast = time(NULL);
				return true;
			}
		}

		{
			wxCriticalSectionLocker locker(m_csJobsGuard);

			JOBLIST::iterator i = m_jobs.find( lIP );

			if ( i!=m_jobs.end() )
			{
				i->second.m_lstCookies.push_back( pCookie );
				i->second.m_lstCookies.unique();
			}
			else
			{
				JOBENTRY entry;
				entry.tLast = time(NULL);
				entry.lIp = lIP;
				entry.m_lstCookies.push_back(pCookie);

				if ( m_jobs.size()>MAX_JOB_SIZE )
				{
					time_t t=time(NULL);
					t-=60*60;

					while (!ShrinkDB( m_jobs, t, MAX_JOB_SIZE ) )  
					{
						t+=60;
					}
				}

				m_jobs[lIP]= entry;
			}
		}
	}
	catch(...)
	{

	}

	return false;
}